<?php

namespace app\pay\service;

use app\common\exception\BusinessException;
use app\common\model\AppConfig;
use think\facade\Config;
use WeChatPay\Builder;
use WeChatPay\Crypto\AesGcm;
use WeChatPay\Crypto\Rsa;
use WeChatPay\Formatter;
use WeChatPay\Util\PemUtil;

/**
 * 企业小程序微信支付
 */
class WxCorpMcpPayService
{
    /**
     * 租户编号
     * @var string
     */
    protected string $appId;

    /**
     * 小程序支付配置
     * @var array
     */
    protected array $wxMcpConfig;

    /**
     * getInstance
     * @return \WeChatPay\BuilderChainable
     * @throws BusinessException
     */
    public function getInstance()
    {
        // 获取配置信息
        $wxMcpPayConfig = $this->getWxMcpPayConfig();

        // 商户号
        $merchantId = $wxMcpPayConfig['mch_id'];

        // 从本地文件中加载「商户API私钥」，「商户API私钥」会用来生成请求的签名
        $merchantPrivateKeyFilePath = $wxMcpPayConfig['merchant_private_key'];
        $merchantPrivateKeyInstance = Rsa::from($merchantPrivateKeyFilePath, Rsa::KEY_TYPE_PRIVATE);

        // 「商户API证书」的「证书序列号」
        $merchantCertificateSerial = $wxMcpPayConfig['certificate_serial_number'];

        // 从本地文件中加载「微信支付平台证书」，用来验证微信支付应答的签名
        $platformCertificateFilePath = $wxMcpPayConfig['platform_public_key'];
        $platformPublicKeyInstance = Rsa::from($platformCertificateFilePath, Rsa::KEY_TYPE_PUBLIC);

        // 读取微信支付平台公钥编号,证书和编号必须匹配
        $platformCertificateSerial = $wxMcpPayConfig['platform_public_key_no'];

        // 构造客户端实例
        $instance = Builder::factory([
            'mchid' => $merchantId,
            'serial' => $merchantCertificateSerial,
            'privateKey' => $merchantPrivateKeyInstance,
            'certs' => [
                $platformCertificateSerial => $platformPublicKeyInstance,
            ],
        ]);

        return $instance;
    }

    /**
     * getWxMcpPayConfig
     * @throws BusinessException
     */
    public function getWxMcpPayConfig(): array
    {
        if (!empty($this->wxMcpConfig)) {
            return $this->wxMcpConfig;
        }
        $appConfigModel = new AppConfig();
        $this->wxMcpConfig = $appConfigModel->getByIdAndType($this->appId, 'wx_mcp_pay');
        if (!$this->wxMcpConfig) {
            throw new BusinessException('租户未配置微信小程序支付参数');
        }
        return $this->wxMcpConfig;
    }

    /**
     * 设置租户编号
     * @param string $appId
     * @return $this
     */
    public function setAppId(string $appId): WxCorpMcpPayService
    {
        $this->appId = $appId;
        return $this;
    }

    /**
     * 获取小程序配置
     * @return array
     * @throws BusinessException
     */
    private function getWxMcpConfig()
    {
        $appConfigModel = new AppConfig();
        $config = $appConfigModel->getByIdAndType($this->appId, 'wx_mcp');
        if (!$config) {
            throw new BusinessException('此应用未配置小程序参数');
        }
        return $config;
    }

    /**
     * buildPayData(原生小程序企业支付)
     */
    public function buildPayData(string $openid, string $out_trade_no, float $total_fee, string $goods_name): array
    {
        // 支付配置
        $instance = $this->getInstance();
        $wxMcpConfig = $this->getWxMcpConfig();
        $wxMcpPayConfig = $this->getWxMcpPayConfig();

        // 请求参数
        $amount = intval(bcmul($total_fee, 100));
        $requestData = [
            'appid' => $wxMcpConfig['appId'],
            'mchid' => $wxMcpPayConfig['mch_id'],
            'description' => $goods_name,
            'out_trade_no' => $out_trade_no,
            'notify_url' => $wxMcpPayConfig['notify_url'],
            'amount' => [
                'total' => $amount,
            ],
            'payer' => [
                'openid' => $openid,
            ],
        ];

        $resp = $instance
            ->chain('v3/pay/transactions/jsapi')
            ->post(['json' => $requestData]);
        $statusCode = $resp->getStatusCode();
        if ($statusCode != 200) {
            throw new BusinessException('调用微信支付失败');
        }
        $response = $resp->getBody();
        $responseData = json_decode($response, true);
        if (!$responseData) {
            throw new BusinessException('解析微信支付响应失败');
        }

        // 生成签名
        $params = [
            'appId' => $wxMcpConfig['appId'],
            'timeStamp' => (string)Formatter::timestamp(),
            'nonceStr' => Formatter::nonce(),
            'package' => 'prepay_id=' . $responseData['prepay_id'],
        ];

        $merchantPrivateKeyInstance = Rsa::from($wxMcpPayConfig['merchant_private_key']);
        $params += ['paySign' => Rsa::sign(
            Formatter::joinedByLineFeed(...array_values($params)),
            $merchantPrivateKeyInstance
        ), 'signType' => 'RSA'];

        // 返回
        $params['partnerId'] = $wxMcpPayConfig['mch_id'];
        $params['prepayId'] = $responseData['prepay_id'];
        return $params;
    }

    /**
     * getNotifyData
     * @throws BusinessException
     */
    public function getNotifyData($inWechatPayTimestamp, $inWechatPayNonce, $inBody, $inWechatPaySignature)
    {
        // 检查通知时间偏移量，允许5分钟之内的偏移
        $timeOffsetStatus = 300 * 1000 >= abs(Formatter::timestamp() - (int)$inWechatPayTimestamp);
        if (!$timeOffsetStatus) {
            throw new BusinessException('回调信息已超时');
        }

        // 检查签名
        $wxMcpPayConfig = $this->getWxMcpPayConfig();
        $platformPublicKeyInstance = Rsa::from($wxMcpPayConfig['platform_public_key'], Rsa::KEY_TYPE_PUBLIC);
        $verifiedStatus = Rsa::verify(
            Formatter::joinedByLineFeed($inWechatPayTimestamp, $inWechatPayNonce, $inBody),
            $inWechatPaySignature,
            $platformPublicKeyInstance
        );
        if (!$verifiedStatus) {
            throw new BusinessException('签名校验不通过');
        }

        // 转换通知的JSON文本消息为PHP Array数组
        $inBodyArray = (array)json_decode($inBody, true);

        // 使用PHP7的数据解构语法，从Array中解构并赋值变量
        ['resource' => [
            'ciphertext' => $ciphertext,
            'nonce' => $nonce,
            'associated_data' => $aad
        ]] = $inBodyArray;

        // 加密文本消息解密
        $inBodyResource = AesGcm::decrypt($ciphertext, $wxMcpPayConfig['api_key'], $nonce, $aad);

        // 返回
        return (array)json_decode($inBodyResource, true);
    }

}